#cython: language_level=3

'''
    Software License

    Copyright (C) 2021-05-24  Xoronos

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, version 3.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <https://www.gnu.org/licenses/>.
'''

'''
    Liabilities

    The software is provided "AS IS" without any warranty of any kind, either expressed,
    implied, or statutory, including, but not limited to, any warranty that the software
    will conform to specifications, any implied warranties of merchantability, fitness
    for a particular purpose, and freedom from infringement, and any warranty that the
    documentation will conform to the software, or any warranty that the software will
    be error free.

    In no event shall Xoronos be liable for any damages, including, but not limited to,
    direct, indirect, special or consequential damages, arising out of, resulting from,
    or in any way connected with this software, whether or not based upon warranty,
    contract, tort, or otherwise, whether or not injury was sustained by persons or
    property or otherwise, and whether or not loss was sustained from, or arose out of
    the results of, or use of, the software or services provided hereunder.
    
    To request the provided software under a different license you can contact us at
    support@xoronos.com
'''


import numpy as np
cimport numpy as np

# Function to get a bit from a number
cpdef int get_bit(unsigned long long number, int bit_length, int bit_position):
    cdef unsigned long long mask = np.uint64(2) ** np.uint64(bit_position)
    return (number & mask) >> bit_position

# Function to set a bit in a number
cpdef unsigned long long set_bit(unsigned long long number, int bit_length, int bit_position, int bit_value):
    cdef unsigned long long mask = np.uint64(2) ** np.uint64(bit_position)
    if bit_value == 1:
        return number | mask
    else:
        return number & ~mask

# Function to split MSBs and LSBs
cpdef tuple msb_lsb_split(np.ndarray[unsigned long long, ndim=1] input_array, int bitsize, int bit_skip):
    cdef list msbs_array = []
    cdef list lsbs_array = []
    cdef int msbs_bitpos = 0
    cdef int lsbs_bitpos = 0
    cdef int msbs_bytepos = 0
    cdef int lsbs_bytepos = 0
    cdef int sign_exponent_bits
    cdef int bit_pos, bit, found_msb, after_msb
    cdef unsigned long long element

    lsbs_array.append(0)
    msbs_array.append(0)

    sign_exponent_bits = bit_skip

    for element in input_array:
        found_msb = 0
        after_msb = 0
        for bit_pos in range(bitsize - 1, -1, -1):
            bit = get_bit(element, bitsize, bit_pos)
            # copy the exponent and sign bits into msbs array
            if ((bitsize - 1 - bit_pos) < sign_exponent_bits):
                msbs_array[msbs_bytepos] = set_bit(msbs_array[msbs_bytepos], 8, msbs_bitpos, bit)
                msbs_bitpos = (msbs_bitpos + 1) % 8
                if msbs_bitpos == 0:
                    msbs_array.append(0)
                    msbs_bytepos = msbs_bytepos + 1
            else:
                if bit == 1:
                    found_msb = 1
                if after_msb <= 2:
                    msbs_array[msbs_bytepos] = set_bit(msbs_array[msbs_bytepos], 8, msbs_bitpos, bit)
                    msbs_bitpos = (msbs_bitpos + 1) % 8
                    if msbs_bitpos == 0:
                        msbs_array.append(0)
                        msbs_bytepos = msbs_bytepos + 1
                    if found_msb == 1:
                        after_msb = after_msb + 1
                else:
                    lsbs_array[lsbs_bytepos] = set_bit(lsbs_array[lsbs_bytepos], 8, lsbs_bitpos, bit)
                    lsbs_bitpos = (lsbs_bitpos + 1) % 8
                    if lsbs_bitpos == 0:
                        lsbs_array.append(0)
                        lsbs_bytepos = lsbs_bytepos + 1
    return msbs_array, lsbs_array

# Function to join MSBs and LSBs
cpdef np.ndarray msb_lsb_join(np.ndarray[unsigned char, ndim=1] msbs_array, np.ndarray[unsigned char, ndim=1] lsbs_array, int num_el, int bitsize, int bit_skip):

    cdef np.ndarray joined_array
    cdef int msbs_bytepos = 0
    cdef int join_elpos = 0
    cdef int lsbs_bytepos = 0
    cdef int msbs_bitpos = 0
    cdef int lsbs_bitpos = 0
    cdef int join_bitpos = bitsize
    cdef int found_msb, after_msb, first_bit, i, bit
    cdef int sign_exponent_bits
    cdef int sign_exponent_bits_cnt = 0

    sign_exponent_bits = bit_skip

    if bitsize <= 8:
        joined_array = np.zeros(num_el, dtype=np.uint8)
    elif bitsize <= 16:
        joined_array = np.zeros(num_el, dtype=np.uint16)
    elif bitsize <= 32:
        joined_array = np.zeros(num_el, dtype=np.uint32)
    elif bitsize <= 64:
        joined_array = np.zeros(num_el, dtype=np.uint64)

    for i in range(num_el):
        found_msb = 0
        after_msb = 0
        first_bit = 0
        sign_exponent_bits_cnt = 0
        while (found_msb == 0) and ((join_bitpos != bitsize) or (first_bit == 0)):
            bit = get_bit(msbs_array[msbs_bytepos], 8, msbs_bitpos)
            joined_array[join_elpos] = set_bit(joined_array[join_elpos], bitsize, join_bitpos - 1, bit)
            msbs_bitpos = (msbs_bitpos + 1) % 8
            if msbs_bitpos == 0:
                msbs_bytepos = msbs_bytepos + 1
            join_bitpos = join_bitpos - 1
            if join_bitpos == 0:
                join_bitpos = bitsize
                join_elpos = join_elpos + 1
            # copy the exponent and sign bits into msbs array
            if sign_exponent_bits_cnt >= sign_exponent_bits :
                if bit == 1:
                    found_msb = 1
            else : 
                sign_exponent_bits_cnt = sign_exponent_bits_cnt + 1
            first_bit = 1
        while (after_msb < 2) and (join_bitpos != bitsize):
            bit = get_bit(msbs_array[msbs_bytepos], 8, msbs_bitpos)
            joined_array[join_elpos] = set_bit(joined_array[join_elpos], bitsize, join_bitpos - 1, bit)
            msbs_bitpos = (msbs_bitpos + 1) % 8
            if msbs_bitpos == 0:
                msbs_bytepos = msbs_bytepos + 1
            join_bitpos = join_bitpos - 1
            if join_bitpos == 0:
                join_bitpos = bitsize
                join_elpos = join_elpos + 1
            after_msb = after_msb + 1
        while join_bitpos != bitsize:
            bit = get_bit(lsbs_array[lsbs_bytepos], 8, lsbs_bitpos)
            joined_array[join_elpos] = set_bit(joined_array[join_elpos], bitsize, join_bitpos - 1, bit)
            lsbs_bitpos = (lsbs_bitpos + 1) % 8
            if lsbs_bitpos == 0:
                lsbs_bytepos = lsbs_bytepos + 1
            join_bitpos = join_bitpos - 1
            if join_bitpos == 0:
                join_bitpos = bitsize
                join_elpos = join_elpos + 1
    return joined_array
